//Copyright (c) 2007, Moq Contributors
//http://code.google.com/p/moq-contrib/
//All rights reserved.

//Redistribution and use in source and binary forms, 
//with or without modification, are permitted provided 
//that the following conditions are met:

//    * Redistributions of source code must retain the 
//    above copyright notice, this list of conditions and 
//    the following disclaimer.

//    * Redistributions in binary form must reproduce 
//    the above copyright notice, this list of conditions 
//    and the following disclaimer in the documentation 
//    and/or other materials provided with the distribution.

//    * Neither the name of the Moq Contributors nor the 
//    names of its contributors may be used to endorse 
//    or promote products derived from this software 
//    without specific prior written permission.

//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 
//CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 
//INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
//MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
//SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
//BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
//INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
//WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
//OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
//SUCH DAMAGE.

//[This is the BSD license, see
// http://www.opensource.org/licenses/bsd-license.php]

using System;

using Autofac;

using AutofacContrib.CommonServiceLocator;

using Microsoft.Practices.ServiceLocation;

namespace Moq.Contrib.Indy
{
	/// <summary>
	/// Auto-mocking factory that can create an instance of the class under test
	/// and automatically inject mocks for all its dependencies.
	/// </summary>
	public class AutoMockContainer : IMockContainer
	{

		private readonly IContainer _container;
		private readonly Lazy<IServiceLocator> _locator;

		/// <summary>
		/// Initializes the container with the <see cref="MockRepository"/> that
		/// will be used to create dependent mocks.
		/// </summary>
		/// <param name="repository">The repository.</param>
		public AutoMockContainer(MockRepository repository)
		{
			_locator = new Lazy<IServiceLocator>(GetLocator);
			var builder = new ContainerBuilder();
			builder.RegisterInstance(this).OwnedByLifetimeScope();
			_container = builder.Build();
			_container.ComponentRegistry.AddRegistrationSource(new MoqRegistrationSource(repository));
		}

		#region IMockContainer Members

		/// <summary>
		/// Resolves a required service of the given type.
		/// </summary>
		public T Resolve<T>()
		{
			return _container.Resolve<T>();
		}

		/// <summary>
		/// Gets or creates a mock for the given type, with 
		/// the default behavior specified by the factory.
		/// </summary>
		public Mock<T> GetMock<T>() where T : class
		{
			var obj = (IMocked<T>)Create<T>();
			return obj.Mock;
		}

		/// <summary>
		/// Creates an instance of a class under test, 
		/// injecting all necessary dependencies as mocks.
		/// </summary>
		/// <typeparam name="T">Requested object type.</typeparam>
		public T Create<T>()
		{
			return ResolveOrCreate<T>(builder => builder.RegisterType<T>().PropertiesAutowired(PropertyWiringFlags.PreserveSetValues));
		}

		/// <summary>
		/// Creates an instance of a class under test using 
		/// the given activator function.
		/// </summary>
		/// <typeparam name="T">Requested object type.</typeparam>
		public T Create<T>(Func<IMockContainer, T> activator)
		{
			return ResolveOrCreate<T>(builder => builder.Register(c => activator(this)).PropertiesAutowired(PropertyWiringFlags.PreserveSetValues));
		}

		/// <summary>
		/// Registers the given service on the container.
		/// </summary>
		/// <typeparam name="TService">Service</typeparam>
		/// <typeparam name="TImplementation">The implementation of the service.</typeparam>
		public void Register<TService, TImplementation>()
		{
			Update(builder => builder.RegisterType<TImplementation>().As<TService>().PropertiesAutowired(PropertyWiringFlags.PreserveSetValues));
		}

		/// <summary>
		/// Registers the given service instance on the container.
		/// </summary>
		/// <typeparam name="TService">Service type.</typeparam>
		/// <param name="instance">Service instance.</param>
		public void Register<TService>(TService instance) where TService : class
		{
			Update(builder => builder.RegisterInstance(instance).As<TService>().PropertiesAutowired(PropertyWiringFlags.PreserveSetValues));
		}

		/// <summary>
		/// Registers the given service creation delegate on the container.
		/// </summary>
		/// <typeparam name="TService">Service type.</typeparam>
		public void Register<TService>(Func<IMockContainer, TService> activator)
		{
			Update(builder => builder.Register(c => activator(this)).As<TService>().PropertiesAutowired(PropertyWiringFlags.PreserveSetValues));
		}

		public IServiceLocator Locator
		{
			get { return _locator.Value; }
		}

		#endregion

		private IServiceLocator GetLocator()
		{
			return new AutofacServiceLocator(_container);
		}

		private void Update(Action<ContainerBuilder> registration)
		{
			var builder = new ContainerBuilder();
			registration(builder);
			builder.Update(_container);
		}

		private T ResolveOrCreate<T>(Action<ContainerBuilder> registration)
		{
			if (!_container.IsRegistered<T>()) Update(registration);
			return _container.Resolve<T>();
		}
	}
}